/**
  ******************************************************************************
  * @file    command_at.c
  * @author  ashuai0110
  * @version V2.4
  * @date    2024-09-10
  * @brief   AT指令交互
  *
  ******************************************************************************
  * @attention
  *
  * 版权声明:内容为编者(ashuai0110)原创,使用请注明出处,当然,你也可以不这样做^_^
  * 出处链接:https://gitee.com/ashuai0110/mcu_reuse_development_module.git
  *
  * Change Logs:
  * Date          Author        Notes
  * 2024-02-20    ashuai0110    完成基本内容
  * 2024-02-24    ashuai0110    修改AT交互处理为格式化内容输入,增加轮询式交互函数
  * 2024-03-04    ashuai0110    修改重发次数和回复超时为独立参数并增加其读写接口
  * 2024-03-17    ashuai0110    修改前缀字符串可以为NULL
  * 2024-08-31    ashuai0110    修改超时判断逻辑,删除一些assert
  *
  ******************************************************************************
  */

/* 包含头文件-----------------------------------------------------------------*/
#include "command_at.h"

/**
 * @addtogroup modules
 * @{
 */

/**
 * @defgroup command_at command_at
 * @{
 */

/* 私有宏定义-----------------------------------------------------------------*/
/**
 * @defgroup command_at_local_macros command at local macros
 * @{
 */

/**
 * @defgroup command_at_check_parameters_validity command at check parameters validity
 * @{
 */
/* 前后缀保留标志有效性检查 */
#define IS_VALID_RESERVE(x)                                                   \
(       ((x) == CMD_AT_NOT_RESERVE)    ||                                     \
        ((x) == CMD_AT_RESERVE))
/* 处理方式有效性检查 */
#define IS_VALID_WAY(x)                                                       \
(       ((x) == CMD_AT_WAY_SEARCH)     ||                                     \
        ((x) == CMD_AT_WAY_REC_FLAG))
/**
 * @}
 */

/**
 * @}
 */

/* 私有类型定义---------------------------------------------------------------*/

/* 私有变量-------------------------------------------------------------------*/

/* 全局变量-------------------------------------------------------------------*/

/* 私有函数原型---------------------------------------------------------------*/


/**
 * @defgroup command_at_global_functions command at global functions
 * @{
 */

/**
  * @brief  AT指令交互初始化
  *
  * @param  _at         : AT指令管理实例
  *
  * @param  head        : 前缀字符串(允许前缀为NULL)
  *
  * @param  tail        : 后缀字符串
  *
  * @param  readFn      : 读取数据函数
  *
  * @param  writeFn     : 写入数据函数
  *
  * @param  rspTimeout  : 回复超时时间ms
  *
  * @param  resendTimes : 重发次数
  *
  * @retval None
  */
void cmd_at_init(cmd_at_t *_at, const char *head, const char *tail, mw_rw_fn_t readFn, mw_rw_fn_t writeFn, uint32_t rspTimeout, uint8_t resendTimes)
{
    MW_ASSERT_PARAM(IS_VALID_POINTER(_at));
    MW_ASSERT_PARAM(IS_VALID_POINTER(tail));
    MW_ASSERT_PARAM(IS_VALID_POINTER(readFn));
    MW_ASSERT_PARAM(IS_VALID_POINTER(writeFn));
    
    /* 初始化各参数 */
    memset(_at, 0, sizeof(cmd_at_t));
    if(NULL != head) { strcpy(_at->head, head); }
    strcpy(_at->tail, tail);
    _at->readFn = readFn;
    _at->writeFn = writeFn;
    _at->rspTimeout = rspTimeout;
    _at->resendTimes = resendTimes;
}

/**
  * @brief  AT指令交互阻塞式处理
  *
  * @note   若处理方式使用外部接收标志,则接收标志应在中断中处理,因为此函数是阻塞的
  *
  * @param  _at                 : AT指令管理实例
  *
  * @param  reserve             : 是否保留前缀和后缀 @ref command_at_reserve
  *   @arg  CMD_AT_NOT_RESERVE  : 不保留
  *   @arg  CMD_AT_RESERVE      : 保留
  *
  * @param  way                 : 处理方式 @ref command_at_way
  *   @arg  CMD_AT_WAY_SEARCH   : 搜索前后缀
  *   @arg  CMD_AT_WAY_REC_FLAG : 外部接收标志
  *
  * @param  ret                 : 期望的回复
  *   @arg  NULL                : 填写NULL表示不关心回复内容,一般在发送读取命令时使用
  *   @arg  !NULL               : 填写非NULL表示关心回复内容,回复内容中包含(而非完全相同)期望内容即返回成功
  *
  * @param  cmdFmt              : 发送的命令(完整的命令,注意格式化"AT+NAME=%s\r\n", "ashuai")
  *
  * @retval 返回回复结果
  *    @arg MW_RET_OK           : 成功,回复内容符合期望
  *    @arg MW_RET_ERR          : 失败,回复内容不符合期望
  *    @arg MW_RET_TIMEOUT      : 失败,回复超时
  */
uint8_t cmd_at_process_block(cmd_at_t *_at, uint8_t reserve, uint8_t way, const char *ret, const char *cmdFmt, ...)
{
    uint16_t len;
    char *newStr;
    va_list args;
    
    while(1)
    {
        switch(_at->state)
        {
            case CMD_AT_STATE_FORMAT_CMD:
                /* 格式化内容 */
                va_start(args, cmdFmt);
                _at->cmdLen = vsnprintf(_at->cmdBuf, sizeof(_at->cmdBuf), cmdFmt, args);
                va_end(args);
                _at->state = CMD_AT_STATE_SEND_CMD; /* 跳转状态至命令发送 */
            case CMD_AT_STATE_SEND_CMD:
                _at->dataLen = 0; /* 清零数据长度 */
                _at->recFlag = 0; /* 清零接收标志 */
                _at->writeFn((void *)_at->cmdBuf, _at->cmdLen); /* 发送命令 */
                _at->state = CMD_AT_STATE_WAIT_RSP; /* 跳转状态至等待回复 */
                _at->rspTimeoutCnt = mw_tick_get(); /* 开启回复超时计数 */
                break;
            case CMD_AT_STATE_WAIT_RSP:
                /* 回复时间超出预设值 回复超时 */
                if((mw_tick_get() - _at->rspTimeoutCnt) > _at->rspTimeout)
                {
                    _at->state = CMD_AT_STATE_SEND_CMD; /* 跳转状态至发送命令 */
                    _at->resendTimesCnt++; /* 累加重发次数 */
                    /* 若重发次数超出预设值则返回MW_RET_TIMEOUT */
                    if(_at->resendTimes < _at->resendTimesCnt)
                    {
                        _at->resendTimesCnt = 0; /* 清零重发次数 */
                        _at->state = CMD_AT_STATE_FORMAT_CMD; /* 初始化状态 */
                        
                        return MW_RET_TIMEOUT;
                    }
                } /* 回复未超时 */
                else
                {
                    /* 搜索前后缀的处理方式 */
                    if(CMD_AT_WAY_SEARCH == way)
                    {
                        /* 读取回复内容 */
                        len = _at->readFn((void *)&_at->dataBuf[_at->dataLen], sizeof(_at->dataBuf) - _at->dataLen);
                        if(0 == len) { break; } /* 未读到直接break */
                        /* 累计数据长度并添加结束符 */
                        _at->dataLen += len;
                        _at->dataBuf[_at->dataLen] = '\0';
                        /* 数据长度大于前后缀总长度则进行检索 */
                        if(_at->dataLen > (strlen(_at->head) + strlen(_at->tail)))
                        {
                            newStr = strstr(_at->dataBuf, _at->head); /* 检索是否包含前缀 */
                            /* 返回不为NULL说明包含前缀 */
                            if(NULL != newStr)
                            {
                                _at->startIndex = newStr - _at->dataBuf; /* 记录前缀索引 */
                                newStr = strstr(newStr, _at->tail); /* 检索是否包含后缀 */
                                /* 返回不为NULL说明包含后缀 */
                                if(NULL != newStr)
                                {
                                    _at->stopIndex = newStr - _at->dataBuf; /* 记录后缀索引 */
                                    _at->state = CMD_AT_STATE_DATA_CHECK; /* 跳转状态至校验回复 */
                                }
                            }
                        }
                    } /* 外部接收标志的处理方式 */
                    else
                    {
                        if(_at->recFlag)
                        {
                            _at->recFlag = 0; /* 清零接收标志 */
                            /* 读取回复内容 */
                            do {
                                len = _at->readFn((void *)&_at->dataBuf[_at->dataLen], sizeof(_at->dataBuf) - _at->dataLen);
                                _at->dataLen += len; /* 累计数据长度 */
                            } while(len);
                            _at->dataBuf[_at->dataLen] = '\0'; /* 添加结束符 */
                            _at->state = CMD_AT_STATE_DATA_CHECK; /* 跳转状态至校验回复 */
                        }
                    }
                }
                break;
            case CMD_AT_STATE_DATA_CHECK:
                /* 搜索前后缀的处理方式 */
                if(CMD_AT_WAY_SEARCH == way)
                {
                    /* 不保留前缀后缀 */
                    if(CMD_AT_NOT_RESERVE == reserve)
                    {
                        _at->startIndex += strlen(_at->head);
                        _at->dataLen = _at->stopIndex - _at->startIndex;
                    } /* 保留前缀后缀 */
                    else
                    {
                        _at->dataLen = _at->stopIndex - _at->startIndex + strlen(_at->tail);
                    }
                    /* 截取数据并从头保存 */
                    for(len = 0; len < _at->dataLen; len++)
                    {
                        _at->dataBuf[len] = _at->dataBuf[_at->startIndex++];
                    }
                    _at->dataBuf[len] = '\0'; /* 添加结束符 */
                }
                /* 恢复默认参数 */
                _at->state = CMD_AT_STATE_FORMAT_CMD;
                _at->resendTimesCnt = 0;
                /* 期望回复不是NULL则校验回复内容 */
                if(NULL != ret)
                {
                    /* 回复内容中包含(而非完全相同)期望回复 则返回MW_RET_OK 否则返回MW_RET_ERR */
                    newStr = strstr(_at->dataBuf, ret);
                    if(NULL != newStr)
                    {
                        return MW_RET_OK;
                    }
                    else
                    {
                        return MW_RET_ERR;
                    }
                }
                
                return MW_RET_OK;
        }
    }
}

/**
  * @brief  AT指令交互轮询式处理(非阻塞式)
  *
  * @note   以状态机架构轮询处理
  *
  * @param  _at                 : AT指令管理实例
  *
  * @param  reserve             : 是否保留前缀和后缀 @ref command_at_reserve
  *   @arg  CMD_AT_NOT_RESERVE  : 不保留
  *   @arg  CMD_AT_RESERVE      : 保留
  *
  * @param  way                 : 处理方式 @ref command_at_way
  *   @arg  CMD_AT_WAY_SEARCH   : 搜索前后缀
  *   @arg  CMD_AT_WAY_REC_FLAG : 外部接收标志
  *
  * @param  ret                 : 期望的回复
  *   @arg  NULL                : 填写NULL表示不关心回复内容,一般在发送读取命令时使用
  *   @arg  !NULL               : 填写非NULL表示关心回复内容,回复内容中包含(而非完全相同)期望内容即返回成功
  *
  * @param  cmdFmt              : 发送的命令(完整的命令,注意格式化"AT+NAME=%s\r\n", "ashuai")
  *
  * @retval 返回回复结果
  *    @arg MW_RET_OK           : 成功,回复内容符合期望
  *    @arg MW_RET_ERR          : 失败,回复内容不符合期望
  *    @arg MW_RET_ING          : 轮询处理中
  *    @arg MW_RET_TIMEOUT      : 失败,回复超时
  */
uint8_t cmd_at_process_poll(cmd_at_t *_at, uint8_t reserve, uint8_t way, const char *ret, const char *cmdFmt, ...)
{
    uint16_t len;
    char *newStr;
    va_list args;

    switch(_at->state)
    {
        case CMD_AT_STATE_FORMAT_CMD:
            /* 格式化内容 */
            va_start(args, cmdFmt);
            _at->cmdLen = vsnprintf(_at->cmdBuf, sizeof(_at->cmdBuf), cmdFmt, args);
            va_end(args);
            _at->state = CMD_AT_STATE_SEND_CMD; /* 跳转状态至命令发送 */
        case CMD_AT_STATE_SEND_CMD:
            _at->dataLen = 0; /* 清零数据长度 */
            _at->recFlag = 0; /* 清零接收标志 */
            _at->writeFn((void *)_at->cmdBuf, _at->cmdLen); /* 发送命令 */
            _at->state = CMD_AT_STATE_WAIT_RSP; /* 跳转状态至等待回复 */
            _at->rspTimeoutCnt = mw_tick_get(); /* 开启回复超时计数 */
            break;
        case CMD_AT_STATE_WAIT_RSP:
            /* 回复时间超出预设值 回复超时 */
            if((mw_tick_get() - _at->rspTimeoutCnt) > _at->rspTimeout)
            {
                _at->state = CMD_AT_STATE_SEND_CMD; /* 跳转状态至发送命令 */
                _at->resendTimesCnt++; /* 累加重发次数 */
                /* 若重发次数超出预设值则返回MW_RET_TIMEOUT */
                if(_at->resendTimes < _at->resendTimesCnt)
                {
                    _at->resendTimesCnt = 0; /* 清零重发次数 */
                    _at->state = CMD_AT_STATE_FORMAT_CMD; /* 初始化状态 */
                    
                    return MW_RET_TIMEOUT;
                }
            } /* 回复未超时 */
            else
            {
                /* 搜索前后缀的处理方式 */
                if(CMD_AT_WAY_SEARCH == way)
                {
                    /* 读取回复内容 */
                    len = _at->readFn((void *)&_at->dataBuf[_at->dataLen], sizeof(_at->dataBuf) - _at->dataLen);
                    if(0 == len) { break; } /* 未读到直接break */
                    /* 累计数据长度并添加结束符 */
                    _at->dataLen += len;
                    _at->dataBuf[_at->dataLen] = '\0';
                    /* 数据长度大于前后缀总长度则进行检索 */
                    if(_at->dataLen > (strlen(_at->head) + strlen(_at->tail)))
                    {
                        newStr = strstr(_at->dataBuf, _at->head); /* 检索是否包含前缀 */
                        /* 返回不为NULL说明包含前缀 */
                        if(NULL != newStr)
                        {
                            _at->startIndex = newStr - _at->dataBuf; /* 记录前缀索引 */
                            newStr = strstr(newStr, _at->tail); /* 检索是否包含后缀 */
                            /* 返回不为NULL说明包含后缀 */
                            if(NULL != newStr)
                            {
                                _at->stopIndex = newStr - _at->dataBuf; /* 记录后缀索引 */
                                _at->state = CMD_AT_STATE_DATA_CHECK; /* 跳转状态至校验回复 */
                            }
                        }
                    }
                } /* 外部接收标志的处理方式 */
                else
                {
                    if(_at->recFlag)
                    {
                        _at->recFlag = 0; /* 清零接收标志 */
                        /* 读取回复内容 */
                        do {
                            len = _at->readFn((void *)&_at->dataBuf[_at->dataLen], sizeof(_at->dataBuf) - _at->dataLen);
                            _at->dataLen += len; /* 累计数据长度 */
                        } while(len);
                        _at->dataBuf[_at->dataLen] = '\0'; /* 添加结束符 */
                        _at->state = CMD_AT_STATE_DATA_CHECK; /* 跳转状态至校验回复 */
                    }
                }
            }
            break;
        case CMD_AT_STATE_DATA_CHECK:
            /* 搜索前后缀的处理方式 */
            if(CMD_AT_WAY_SEARCH == way)
            {
                /* 不保留前缀后缀 */
                if(CMD_AT_NOT_RESERVE == reserve)
                {
                    _at->startIndex += strlen(_at->head);
                    _at->dataLen = _at->stopIndex - _at->startIndex;
                } /* 保留前缀后缀 */
                else
                {
                    _at->dataLen = _at->stopIndex - _at->startIndex + strlen(_at->tail);
                }
                /* 截取数据并从头保存 */
                for(len = 0; len < _at->dataLen; len++)
                {
                    _at->dataBuf[len] = _at->dataBuf[_at->startIndex++];
                }
                _at->dataBuf[len] = '\0'; /* 添加结束符 */
            }
            /* 恢复默认参数 */
            _at->state = CMD_AT_STATE_FORMAT_CMD;
            _at->resendTimesCnt = 0;
            /* 期望回复不是NULL则校验回复内容 */
            if(NULL != ret)
            {
                /* 回复内容中包含(而非完全相同)期望回复 则返回MW_RET_OK 否则返回MW_RET_ERR */
                newStr = strstr(_at->dataBuf, ret);
                if(NULL != newStr) { return MW_RET_OK; }
                else { return MW_RET_ERR; }
            }
            
            return MW_RET_OK;
    }
    
    return MW_RET_ING;
}

/**
  * @brief  AT指令回复内容分割(依据前后缀)
  *
  * @note   一般在cmd_at_process()后使用,回复内容形式一般为key-value(键值对)
  *         (cmd_at_process()采用保留前后缀时"AT+NAME=ashuai\r\n" key:"AT+NAME" value:"ashuai\r\n" split:"=")
  *         (cmd_at_process()采用不保留前后缀时"NAME=ashuai" key:"NAME" value:"ashuai" split:"=")
  *
  * @param  _at        : AT指令管理实例
  *
  * @param  key        : key-value中的key字符串暂存区(传入数组地址)
  *
  * @param  value      : key-value中的value字符串暂存区(传入数组地址)
  *
  * @param  split      : 分隔符(一般为"=")
  *
  * @retval 返回分割结果
  *    @arg MW_RET_OK  : 分割成功
  *    @arg MW_RET_ERR : 分割失败,分隔符不存在
  */
uint8_t cmd_at_split(cmd_at_t *_at, char *key, char *value, const char *split)
{
    char *newStr;
    uint8_t tmp1, tmp2;
    
    MW_ASSERT_PARAM(IS_VALID_POINTER(key));
    MW_ASSERT_PARAM(IS_VALID_POINTER(value));
    MW_ASSERT_PARAM(IS_VALID_POINTER(split));

    /* 寻找分隔符 若返回为NULL则直接退出 */
    newStr = strstr(_at->dataBuf, split);
    if(NULL == newStr) { return MW_RET_ERR; }
    tmp1 = newStr - _at->dataBuf; /* 分隔符前的数据数量 = 分隔符地址-数据缓冲区首地址 */
    if(NULL != key)
    {
        memcpy((void *)key, (void *)_at->dataBuf, tmp1); /* 数据拷贝到key字符串暂存区 */
        key[tmp1] = '\0';
    }
    if(NULL != value)
    {
        tmp2 = _at->dataLen - tmp1 - strlen(split); /* 分隔符后的数据数量 = 数据总长 - 分隔符前的数据数量 - 分隔符长度 */
        memcpy((void *)value, (void *)&_at->dataBuf[tmp1 + strlen(split)], tmp2); /* 数据拷贝到value字符串暂存区 */
        value[tmp2] = '\0';
    }
    
    return MW_RET_OK;
}

/**
  * @brief  AT指令获取回复内容
  *
  * @param  _at : AT指令管理实例
  *
  * @retval 返回保存的回复内容
  */
char *cmd_at_rsp_data(cmd_at_t *_at)
{
    return _at->dataBuf;
}

/**
  * @brief  AT指令设置外部接收标志
  *
  * @note   用在处理方式是外部接收标志时,在接收完成的地方调用
  *
  * @param  _at : AT指令管理实例
  *
  * @retval None
  */
void cmd_at_set_rec_flag(cmd_at_t *_at)
{
    _at->recFlag = 1;
}

/**
  * @brief  AT指令获取外部接收标志
  *
  * @param  _at : AT指令管理实例
  *
  * @retval 返回接收标志
  */
uint8_t cmd_at_get_rec_flag(cmd_at_t *_at)
{
    return _at->recFlag;
}

/**
  * @brief  AT指令设置回复超时时间ms
  *
  * @param  _at        : AT指令管理实例
  *
  * @param  rspTimeout : 回复超时时间ms
  *
  * @retval None
  */
void cmd_at_set_rsp_timeout(cmd_at_t *_at, uint32_t rspTimeout)
{
    _at->rspTimeout = rspTimeout; 
}

/**
  * @brief  AT指令获取回复超时时间ms
  *
  * @param  _at : AT指令管理实例
  *
  * @retval 返回回复超时时间ms
  */
uint32_t cmd_at_get_rsp_timeout(cmd_at_t *_at)
{
    return _at->rspTimeout; 
}

/**
  * @brief  AT指令设置重发次数
  *
  * @param  _at         : AT指令管理实例
  *
  * @param  resendTimes : 重发次数
  *
  * @retval None
  */
void cmd_at_set_resend_times(cmd_at_t *_at, uint8_t resendTimes)
{
    _at->resendTimes = resendTimes; 
}

/**
  * @brief  AT指令获取重发次数
  *
  * @param  _at : AT指令管理实例
  *
  * @retval 返回重发次数
  */
uint8_t cmd_at_get_resend_times(cmd_at_t *_at)
{
    return _at->resendTimes; 
}

/**
 * @}
 */

/**
 * @}
 */

/**
 * @}
 */
